package com.hcl.orm.core.executor;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.hcl.orm.core.annotation.SqlCommandType;
import com.hcl.orm.core.dialect.Signature;
import com.hcl.orm.core.dialect.SqlDefinition;
import com.hcl.orm.core.dialect.SqlMetadata;
import com.hcl.orm.core.exception.OrmException;
import com.hcl.orm.core.exception.ParamAssert;
import com.hcl.orm.core.executor.page.PageResult;
import com.hcl.orm.core.spring.DaoMethodCacheManager;
import org.simpleflatmapper.jdbc.spring.JdbcTemplateMapperFactory;
import org.simpleflatmapper.jdbc.spring.RowMapperImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.ResultSetExtractor;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

/**
 * @author hucl
 * @projectName jim-orm
 * @description: TODO
 * @date 2021/5/1414:24
 */
public class DaoMethodExecutor {
    private static final Logger logger = LoggerFactory.getLogger(DaoMethodExecutor.class);
    private final Method method;
    public static JdbcTemplateMapperFactory jdbcTemplateMapperFactory;

    static {
        jdbcTemplateMapperFactory = JdbcTemplateMapperFactory.newInstance()
                .addGetterForType(Character.class, (resultSet, i) -> StrUtil.isEmpty(resultSet.getString(i)) ? null :
                        resultSet.getString(i).charAt(0))
                .ignorePropertyNotFound();
    }


    public DaoMethodExecutor(final Method method) {
        this.method = method;
    }


    public Object execute(JdbcOperations jdbcOperations, Object[] args) {
        SqlDefinition sqlDefinition = DaoMethodCacheManager.gtSqlDefinition(this.method);
        final Signature signature = sqlDefinition.getSignature();
        final SqlMetadata sqlMetadata = sqlDefinition.getSelectedSqlMetadata();
        Object result;
        SqlCommandType commandType = sqlMetadata.getSqlCommandType();
        if (SqlCommandType.INSERT == commandType ||
                SqlCommandType.UPDATE == commandType
                || SqlCommandType.DELETE == commandType) {
            JdbcParam jdbcParam = getJdbcParam(sqlDefinition, args, commandType);
            if (SqlCommandType.INSERT == commandType && jdbcParam.isBatch()) {
                result = jdbcOperations.batchUpdate(jdbcParam.getJdbcSqlTmpl(), (List<Object[]>) jdbcParam.getJdbcArgs());
            } else {
                result = jdbcOperations.update(jdbcParam.getJdbcSqlTmpl(), (Object[]) jdbcParam.getJdbcArgs());
                return rowCountResult(sqlDefinition, (Integer) result);
            }
            return result;
        } else if (SqlCommandType.SELECT == commandType) {
            JdbcParam jdbcParam = getJdbcParam(sqlDefinition, args, commandType);
            if (!jdbcParam.isPage()) {
                if (signature.isReturnsMany()) {
                    Type type = ((ParameterizedType) TypeUtil.getReturnType(this.method)).getActualTypeArguments()[0];
                    ResultSetExtractor mapper = jdbcTemplateMapperFactory.newResultSetExtractor(type);

                    result = jdbcOperations.query(jdbcParam.getJdbcSqlTmpl(),
                            (Object[]) jdbcParam.getJdbcArgs(),
                            mapper
                    );
                    return result;
                } else {
                    RowMapperImpl rowMapper = jdbcTemplateMapperFactory.newRowMapper(signature.getReturnType());
                    try {
                        result = jdbcOperations.queryForObject(jdbcParam.getJdbcSqlTmpl(),
                                (Object[]) jdbcParam.getJdbcArgs(), rowMapper);
                    } catch (EmptyResultDataAccessException ex) {
                        result = null;
                    }
                }
            } else {
                PageResult pageResult = new PageResult();
                Integer count = jdbcOperations.queryForObject(jdbcParam.getCountSqlTmpl(),
                        jdbcParam.getJdbcArgs() == null ? null :
                                (Object[]) jdbcParam.getJdbcArgs(), Integer.class);
                pageResult.setTotal(count);
                if (signature.getPageParamIndex() != null) {
                    Type type = ((ParameterizedType) TypeUtil.getReturnType(this.method)).getActualTypeArguments()[0];
                    RowMapperImpl rowMapper = jdbcTemplateMapperFactory.newRowMapper(type);

                    pageResult.setDatas(jdbcOperations.query(jdbcParam.getJdbcSqlTmpl(),
                            ArrayUtil.addAll((Object[]) jdbcParam.getJdbcArgs(), jdbcParam.getJdbcPageArgs()), rowMapper));
                    pageResult.setPageNo(jdbcParam.getPageParam().getPageNo());
                }
                return pageResult;
            }
        } else {
            throw new OrmException("Unknown execution method for: " + method.getName());
        }
        if (result == null && method.getReturnType().

                isPrimitive() && !signature.isReturnsVoid()) {
            throw new OrmException("Dao method '" + method.getName()
                    + " attempted to return null from a method with a primitive return type (" + signature.getReturnType() + ").");
        }
        return result;
    }

    private JdbcParam getJdbcParam(SqlDefinition sqlDefinition, Object[] args, SqlCommandType commandType) {
        final Signature signature = sqlDefinition.getSignature();
        final SqlMetadata sqlMetadata = sqlDefinition.getSelectedSqlMetadata();
        ParamAssert.hasValidParamWhileSimpleFunction(sqlMetadata.getSqlCommandType(),
                signature);
        if (StrUtil.isEmpty(sqlMetadata.getRawSql())) {
            if (commandType.equals(SqlCommandType.INSERT)) {
                if (!signature.isInputsList()) {
                    return JdbcFormatter.simpleInsertFormat(sqlDefinition, args);
                } else {
                    return JdbcFormatter.simpleBatchInsertFormat(sqlDefinition, args);
                }
            }
            if (commandType.equals(SqlCommandType.UPDATE)) {
                return JdbcFormatter.simpleUpdateFormat(sqlDefinition, args);
            }
            if (commandType.equals(SqlCommandType.DELETE)) {
                return JdbcFormatter.simpleDeleteFormat(sqlDefinition, args);
            }
            if (commandType.equals(SqlCommandType.SELECT)) {
                if (!signature.isReturnsPage()) {
                    return JdbcFormatter.simpleSelectFormat(sqlDefinition, args);
                } else {
                    return JdbcFormatter.simplePageQueryFormat(sqlDefinition, args);
                }
            }
        } else {
            return JdbcFormatter.format(sqlDefinition, args);
        }
        return null;
    }


    private Object rowCountResult(SqlDefinition sqlDefinition, int rowCount) {
        final Object result;
        if (sqlDefinition.getSignature().isReturnsVoid()) {
            result = null;
        } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
            result = rowCount;
        } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
            result = (long) rowCount;
        } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
            result = (rowCount > 0);
        } else {
            throw new OrmException("Dao method '" + this.method.getName() + "' has an unsupported return type: "
                    + sqlDefinition.getSignature().getReturnType());
        }
        return result;
    }
}
